16

JavaScript 中的 new

首先,new 是一个操作符,它可以用来创建两种对象的实例,一种是用户定义的对象类型,另一种则是拥有构造函数的内建对象类型。

创建用户定义的对象需要两个步骤:

  1. 通过编写函数来定义对象类型;
  2. 使用 new 来创建对象实例。

示例一:

var Person = function(personName) {
    this.name = personName;
};

这是一个典型的通过编写函数来定义对象类型的范例,我们可以这样来表述其行为:

Person 函数定义了一种对象类型,其 类型名称 就叫 Person
在使用 Person 函数创建对象实例的时候可以传入变量 personName,该变量会成为对象实例的一个属性,这个属性的名字叫 name
为对象实例定义 name 属性的过程发生在 Person 函数的函数体内;this 即指代将被创建的对象实例。

值得初学者注意的是,在现实中你更多地会看到这样的代码:

var Person = function(name) {
    this.name = name;
};

有些人会搞不清楚究竟哪一个 name 才是对象的属性,在这里详细解释如下:

  1. function(name) 里的 name待传入参数的名字,通常被称作:形式参数(Formal Parameter),或简称 形参 ——因为它只是代表参数的形式而并非真正传入的参数(后者则被称作:实际参数(Actual Parameter),或简称 实参)。
  2. this.namename 是对象的属性名字。
  3. = namename 还是形参,和 1. 里的 name 等价;这就是所谓的 参数传递,或传参

示例二:

var albert = new Person('Albert');
albert.name;       // "Albert"
albert['name'];    // "Albert"

这是承接示例一,使用定义好的 Person 对象类型来实例化对象的范例,这个范例的表述相对容易一些:

定义一个变量 albert,然后实例化一个新的 Person 类型的对象,并将变量 albert 指向这个新的对象。

如果你对 形参实参 还不够清楚的话,看到这里就应该完全明了了。保险起见再加以解释如下:

  1. 在示例二中,new Person('Albert') 中的 'Albert' 即对应着示例一中 function(name) 中的 name,同时也是接下来一行中等号右边的 name
  2. 因此,'Albert' 就是 实际参数name 就是 形式参数

示例二中还演示了两种对象属性的获取方法,分别为 object.propertyobject['property']。前一种比较常用,不过后一种由于可以用字符串来访问对象属性,因此在某些场合下非常有用(比如说用字符串传递了对象的属性)。


回到 new 的话题。

new Person('Albert') 执行的时候,会有如下事情发生:

  1. 创建一个新的对象,其类型是 Person 并继承 Person.prototype 的所有属性。这就是 原型继承
  2. 构造函数 Person 被调用并传入指定的参数(示例二中的 'Albert'),然后 this 被绑定给新创建的对象。另外,若构造函数不需要参数,则 new Person 等价于 new Person()
  3. 若构造函数没有明确的返回值,那么新创建的对象就是整个 new 表达式的结果;反之,若构造函数内显式定义了返回值,则该返回值为整个 new 表达式的结果。

关于第三点,举例示之:

示例三:

var Person = function(name) {
    return {
        name: 'Mr. ' + name
    }
};

var albert = new Person('Albert');

albert.name;        // "Mr. Albert"
var Person = function(name) {
    return {
        rawName: name,
        getName: function(gender) {
            if (gender === 'male') {
                return 'Mr. ' + name;
            } else {
                return 'Mrs. ' + name;
            }
        }
    }
};

var albert = new Person('Albert');

albert.getName('male');        // "Mr. Albert"
albert.getName('female');      // "Mrs. Albert"
var Person = function(name, gender) {
    return {
        rawName: name,
        name: (function() {
            if (gender === 'male') {
                return 'Mr. ' + name;
            } else {
                return 'Mrs. ' + name;
            }
        }())
    }
};

var albert = new Person('Albert', 'male');
albert.rawName;             // "Albert"
albert.name;                // "Mr. Albert"

var annie = new Person('Annie', 'female');
annie.rawName;              // "Annie"
annie.name;                 // "Mrs. Annie"

示例三演示了三种看起来相似但实际上具有显著差异的对象类型定义和对象实例化的例子:

  • 第一种:在 new Person('Albert') 时返回自定义的对象,而不是默认由 new 创建的新对象。在这个自定义对象里,没有简单地把参数 name 赋给属性 this.name,而是做了进一步的修改。这种修改很显然是非常简单但却不够灵活,为了改进它,看下面两个例子:

  • 第二种:同样返回自定义对象,这一次定义了两个属性,一个是 rawName,保存实例化时传递的参数;另一个是 getName,它是一个函数声明,因此不能直接用 albert.getNamealbert['getName'] 来访问(只会返回函数声明本身,但不会有返回值)。不过你可以用albert.getNam('male')albert['getName']('male') 的方式来执行这个函数并求得结果,这就是所谓的 方法
    如果不想用方法调用,但仍然希望像方法声明体内那样做一些逻辑判断是否可以呢?可以,继续看第三种:

  • 第三种:这一次 name 属性又可以像以前那样直接访问了,原因是 name 指向的函数声明使用了 IIFE(Immediately Invoked Function Expression) 技巧,该技巧使得函数声明直接转变成了函数表达式(并即刻执行)。我们知道,函数表达式是能够直接返回值的,而函数声明则需要执行(调用)才能返回值,于是 name 属性获得了返回值,就可以像原来那样直接访问了。
    这种属性定义方式有时被称之为 计算后属性(Computed Property),顾名思义:不是直接返回实例化时传递的值,而是对值进行了一定的处理(计算)之后才返回。


属性与方法:很多人都以为对象有 属性方法,其中属性是可以直接访问到值的,而方法是需要执行才能获得值的。但有的时候也会听到“方法也是属性”这样的说法,这是为什么呢?
其实原因在于对术语的翻译不够准确。英文里的 propertyattribute 都被我们翻译为属性,然而在谈及对象时这两者是不同的。在一个对象里,attributemethod 被统称为 property,直接保存值的 property 称之为 attribute,保存函数声明可以用来执行的 property 才是 method。在用中文描述时很容易把两种“属性”搞混,需要注意分辨清楚。

补充说明:本文发表出去之后,有人感谢我帮他分清了 属性方法 在一些书中的歧义性,也有人拿着别的书来向我表示疑惑。以再版的 Object Oriented in JavaScript 为例,该书中在讲解面向对象基础的时候,明确地指出:保存数据的属性叫做 Property,保存行为的属性叫做 Method,根本就不使用 Attribute。这倒是也简单明了,这样一来就不存在 Method 也是 Property 一说了。

老实说,对此我也不知该如何回应。不同的书用不同的术语,不同的作者也有不同的理解,我没有“统一业界术语”的能量,所以也只能把它们一一列举出来。对于初学者若造成理解上的偏差我表示道歉,总而言之你要记住:一个对象有两类东西:一类记录数据,另一类记录方法(方法总是做一件什么事,其中也包括返回新的数据),至于这两类在不同的情境中叫法不一,也就要靠你自己去分辨了。


n͛i͛g͛h͛t͛i͛r͛e͛
31.1k 声望3.1k 粉丝

正在更新 Elixir 语言的系列文章:[链接]